package com.lagou.edu.factory;

import com.lagou.edu.annotation.Autowired;
import com.lagou.edu.annotation.Service;
import com.lagou.edu.annotation.Transactional;
import com.lagou.edu.service.TransferService;
import com.lagou.edu.service.impl.TransferServiceImpl;

import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.net.URL;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author 应癫
 *
 * 工厂类，生产对象（使用反射技术）
 */
public class BeanFactory {
    private static Map<String,Object> map = new ConcurrentHashMap<>();  // store <class name, singleton obj> mapping
    private static Map<String,String> anoValMap = new ConcurrentHashMap<>(); // store <annotation value,class name> mapping
    private static Map<String,String> interfaceMap = new ConcurrentHashMap<>(); // store <interface name,class name> mapping
    public synchronized static void init() {
        List<String> clNames = new LinkedList<>();
        String basePath = BeanFactory.class.getResource("/").toString();
        if(basePath.startsWith("file:")){
            basePath = basePath.replace("file:","").replace("target/classes/","src/main/java");
            File f = new File(basePath);
            getClassNames(f, basePath, clNames);
        }

        // 1. init beans
        for(String clazz:clNames){
            try {
                Class<?> aClass = Class.forName(clazz);
                if (aClass.getAnnotation(Service.class) instanceof Service) {
                    // check if return proxy object
                    Object obj = aClass.newInstance();
                    if(aClass.getAnnotation(Service.class).type().equals("singleton")){
                        map.put(aClass.getName(),obj);
                    }
                    if(!aClass.getAnnotation(Service.class).value().equals("")){
                        anoValMap.put(aClass.getAnnotation(Service.class).value(), aClass.getName());
                    }
                    Type[] interfaces = aClass.getGenericInterfaces();
                    for(Type type:interfaces){
                        interfaceMap.put(type.getTypeName(), aClass.getName());
                    }
                }
            } catch(ClassNotFoundException | InstantiationException | IllegalAccessException e){
                e.printStackTrace();
            }
        }
        
        // 2. resolve dependency
        for(String key: map.keySet()){
            Object targetObj = map.get(key);
            Field[] fields = targetObj.getClass().getDeclaredFields();
            for(Field field:fields){
                if(field.getAnnotation(Autowired.class) instanceof Autowired){
                    String fieldTypeName = field.getType().getName();
                    field.setAccessible(true);
                    try {
                        if( map.get(fieldTypeName) == null){
                            // if field type not found in map, try find with its implementation
                            fieldTypeName = interfaceMap.get(fieldTypeName);
                        }
                        field.set(targetObj, map.get(fieldTypeName));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        // 3. handle proxy (check if @Transactional)
        for(String key: map.keySet()){
            Object obj = map.get(key);
            obj = handleTransactional(obj);
            map.put(key,obj);
        }
    }

    private static Object handleTransactional(Object obj) {
        Class<?> clazz = obj.getClass();
        if (clazz.getAnnotation(Transactional.class) instanceof Transactional) {
            Type[] interfaces = obj.getClass().getGenericInterfaces();
            if(interfaces.length>0){
                // implemented interfaces, use JdkProxy
                System.out.println("use JdkProxy");
                return ProxyFactory.getInstance().getJdkProxy(obj);
            }
            // no interfaces, use CglibProxy
            System.out.println("use CglibProxy");
            return ProxyFactory.getInstance().getCglibProxy(obj);
        }
        return obj;
    }

    public static void getClassNames(File f, String basePath, List<String> clNames){
        if(f.isFile()&&f.getName().endsWith(".java")){
            String fname = f.getAbsolutePath().replace(basePath,"");
            fname = fname.substring(1).replace(".java","").replace("/",".");
            clNames.add(fname);
            return;
        }
        File[] files = f.listFiles();
        for(File sf:files){
            getClassNames(sf,basePath, clNames);
        }
    }

    public static Object getBean(String name){
        String clazName = name;
        if(anoValMap.containsKey(name)){
            // use annotation value to find class name
            clazName = anoValMap.get(name);
        }
        // no annotation value
        Class<?> aClass = null;
        try {
            aClass = Class.forName(clazName);
            if(aClass.getAnnotation(Service.class).type().equals("singleton")){
                return map.get(clazName);
            }
            return aClass.newInstance();
        } catch (ClassNotFoundException| IllegalAccessException|InstantiationException e) {
            e.printStackTrace();
        }
        return null;
    }

}
